/*
* Copyright 2010 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using System.Linq;
using System.Collections.Generic;
using Ngilead.Test.DAO;
using Ngilead.Test.Models;
using NHibernate.Util;
using System.Runtime.Serialization;
using System.IO;
using Ngilead.Test.Models.Stateless;
using Ngilead.Test.Models.Misc;
using System.Text;
using Iesi.Collections.Generic;
using System.ServiceModel.Description;
using System.Configuration;
using System.ServiceModel;
using Ngilead.Common;
using Ngilead.WCF;

namespace Ngilead.Test
{
    /// <summary>
    /// Static helper class for test
    /// </summary>
    public class TestHelper
    {
        #region Constants

        /// <summary>
        /// The guest login
        /// </summary>
        public static String GUEST_LOGIN = "guest";

        /// <summary>
        /// The JUnit login
        /// </summary>
        public static String JUNIT_LOGIN = "junit";

        /// <summary>
        /// The employee login
        /// </summary>
        public static String EMPLOYEE_LOGIN = "employee";

        /// <summary>
        /// The volumetry test login
        /// </summary>
        public static String VOLUMETRY_LOGIN = "big";

        /// <summary>
        /// The 'test' group
        /// </summary>
        public static String TEST_GROUP = "test";

        /// <summary>
        /// The 'guest' group
        /// </summary>
        public static String GUEST_GROUP = "guests";

        #endregion

        #region NotPersistentClass

        /// <summary>
        /// Not persistent class
        /// </summary>
        public class NotPersistentClass
        {
            /// <summary>
            /// Property
            /// </summary>
            public int Property { get; set; }

            /// <summary>
            /// NotPersistableField
            /// </summary>
            public Random NotPersistableField { get; set; }
        }

        #endregion

        #region Static helper

        #region Serialization

        #region DataContractSerializer

        private static List<Type> _knownTypes;

        private static List<Type> KnownTypes
        {
            get
            {
                if (_knownTypes == null)
                {
                    _knownTypes = new List<Type>()
                    {                        
                        // Models
                        typeof(Address),
                        typeof(Country),
                        typeof(Employee),
                        typeof(Gender),
                        typeof(Group),
                        typeof(Message),
                        typeof(User),
                        typeof(List<User>),                        
                        typeof(HashedSet<Message>),                        
                        
                        // Misc
                        typeof(BaseListLoadResult<User>),
                        typeof(Config),      
                        typeof(Style),
                        typeof(Style.SortDir),

                        // Wrapper
                        typeof(WrappingClass),
                        typeof(WrappingArrayClass),
                        typeof(WrapperWCF),

                        // Other
                        typeof(List<SerializableId>), // ??
                        typeof(String[]),
                        typeof(List<Object>)
                    };
                }
                return _knownTypes;
            }
        }

        /// <summary>
        /// Clone object via DataContractSerializer
        /// </summary>
        /// <param name="obj">Object</param>
        /// <returns>Clone</returns>
        public static Object CloneViaDCS(Object obj)
        {
            return CloneViaDCS(obj, null, null);
        }

        /// <summary>
        /// Clone object via DataContractSerializer
        /// </summary>
        /// <param name="obj">Object</param>
        /// <param name="listToAdd">KnownTypes to add</param>
        /// <param name="listToRemove">KnownTypes to remove</param>
        /// <returns>Clone</returns>
        public static Object CloneViaDCS(Object obj, List<Type> listToAdd, List<Type> listToRemove)
        {
            WrapperWCF wrapper = new WrapperWCF(obj, WrapperWCF.enumTargetAction.Clone);
            String fileName = String.Format(@".\{0} clone.xml", DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss.fff"));
            Object temp = SerializeViaDCS(wrapper, listToAdd, listToRemove, fileName);
            GC.Collect();
            return temp;
        }

        /// <summary>
        /// Merge object via DataContractSerializer
        /// </summary>
        /// <param name="obj">Object</param>
        /// <returns>Merge</returns>
        public static Object MergeViaDCS(Object obj)
        {
            return MergeViaDCS(obj, null, null);
        }

        /// <summary>
        /// Merge object via DataContractSerializer
        /// </summary>
        /// <param name="obj">Object</param>
        /// <param name="listToAdd">KnownTypes to add</param>
        /// <param name="listToRemove">KnownTypes to remove</param>
        /// <returns>Merge</returns>
        public static Object MergeViaDCS(Object obj, List<Type> listToAdd, List<Type> listToRemove)
        {
            WrapperWCF wrapper = new WrapperWCF(obj, WrapperWCF.enumTargetAction.Merge);
            String fileName = String.Format(@".\{0} merge.xml", DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss.fff"));
            Object temp = SerializeViaDCS(wrapper, listToAdd, listToRemove, fileName);
            GC.Collect();
            return temp;
        }

        public static Object SerializeViaDCS(Object obj, List<Type> listToAdd, List<Type> listToRemove, String fileName)
        {
            Object temp = null;

            // datat contract serializer
            DataContractSerializer ngileadDataContractSerializer = new DataContractSerializer(
                typeof(object), GetKnownTypes(listToAdd, listToRemove), 65536, false, true, new NgileadDataContractSurrogate());

            using (MemoryStream stream = new MemoryStream())
            {
                ngileadDataContractSerializer.WriteObject(stream, obj);
                stream.Seek(0, SeekOrigin.Begin);

                StreamReader reader = new StreamReader(stream);
                stream.Seek(0, SeekOrigin.Begin);

                if (!String.IsNullOrEmpty(fileName))
                    WriteXml(fileName, reader);

                stream.Seek(0, SeekOrigin.Begin);
                temp = ngileadDataContractSerializer.ReadObject(stream);
            }

            return temp;
        }

        private static List<Type> GetKnownTypes(List<Type> listToAdd, List<Type> listToRemove)
        {
            List<Type> listType = KnownTypes.ToList();

            // remove
            if (listToRemove != null && listToRemove.Count > 0)
            {
                foreach (Type type in listToRemove)
                {
                    if (listType.Contains(type))
                        listType.Remove(type);
                }
            }

            // add
            if (listToAdd != null && listToAdd.Count > 0)
            {
                foreach (Type type in listToAdd)
                {
                    if (!listType.Contains(type))
                        listType.Add(type);
                }
            }

            return listType;
        }

        private static void WriteXml(String file, StreamReader reader)
        {
            String content = reader.ReadToEnd();
            using (StreamWriter sw = new StreamWriter(file, false, Encoding.Default))
            {
                sw.Write(content);
            }
        }

        #endregion

        #endregion

        #region Services

        #region Create Services

        private static List<ChannelFactory> _listFactory = new List<ChannelFactory>();

        // set data contract serialization operation behavior
        private static void SetDataContractSerializerBehavior(ContractDescription contractDescription)
        {
            foreach (OperationDescription operation in contractDescription.Operations)
            {
                operation.Behaviors.Add(new NgileadDataContractSerializerOperationBehavior(operation));
            }
        }

        /// <summary>
        /// Create service
        /// </summary>
        /// <typeparam name="TServiceContract">Service contract</typeparam>
        /// <typeparam name="TService">Service</typeparam>
        /// <param name="ServiceName">Service name</param>
        public static TServiceContract CreateService<TServiceContract, TService>(String ServiceName)
        {
            TServiceContract Contract;

            if (Boolean.Parse(ConfigurationManager.AppSettings["wcfservice"]))
            {
                String BaseAddress = ConfigurationManager.AppSettings["servicesBaseAddress"];
                try
                {
                    BasicHttpBinding binding = new BasicHttpBinding();
                    binding.Security.Mode = BasicHttpSecurityMode.None;
                    //binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
                    //binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
                    binding.MaxReceivedMessageSize = 2147483647;

                    EndpointAddress address = new EndpointAddress(string.Concat(BaseAddress, NhibernateContext.Context, ServiceName));
                    ChannelFactory<TServiceContract> factory = new ChannelFactory<TServiceContract>(binding, address);
                    SetDataContractSerializerBehavior(factory.Endpoint.Contract);
                    Contract = factory.CreateChannel();
                    _listFactory.Add(factory);
                }
                catch (Exception)
                {
                    throw;
                }
            }
            else
            {
                Contract = (TServiceContract)Activator.CreateInstance(typeof(TService));
            }

            return Contract;
        }

        /// <summary>
        /// Close all channels
        /// </summary>
        public static void CloseAllChannels()
        {
            foreach (ChannelFactory factory in _listFactory)
            {
                if (factory != null && factory.State != CommunicationState.Faulted)
                    factory.Close();
                if (factory != null && factory.State != CommunicationState.Closed)
                    factory.Abort();
            }
        }

        #endregion

        #endregion

        #region Database

        /// <summary>
        /// Check that the database is initialized
        /// </summary>
        /// <returns>true/false</returns>
        public static bool IsInitialized()
        {
            return (DAOFactory.UserDAO.CountAll() > 0);
        }

        /// <summary>
        /// Database initialization
        /// </summary>
        public static void InitializeDB()
        {
            //  Gender
            Gender genderFemale = CreateGender("F");
            DAOFactory.GenderDAO.Save(genderFemale);

            Gender genderMale = CreateGender("M");
            DAOFactory.GenderDAO.Save(genderMale);

            Gender genderUnknown = CreateGender("U");
            DAOFactory.GenderDAO.Save(genderUnknown);

            //	Create guest & test group
            //
            Group guestGroup = CreateGroup();
            guestGroup.Name = GUEST_GROUP;

            Group testGroup = CreateGroup();
            testGroup.Name = TEST_GROUP;

            //	Create guest user (no password)
            //
            User guestUser = CreateUser();
            guestUser.Login = GUEST_LOGIN;
            guestUser.FirstName = "No";
            guestUser.LastName = "name";

            guestUser.Gender = genderFemale;

            // address
            Address address = CreateAddress();
            address.Street = "Baker street";
            address.City = "London";
            address.Country = CreateCountry();
            address.Country.Name = "England";
            guestUser.Address = address;

            // create welcome message
            Message guestMessage = CreateMessage();
            guestMessage.Text = "Welcome in Gilead sample application";
            guestMessage.Date = DateTime.Now;
            guestMessage.Author = guestUser;
            ComputeKeywords(guestMessage);
            guestUser.MessageList = new HashedSet<Message>();
            guestUser.MessageList.Add(guestMessage);

            // group
            guestUser.GroupList = new List<Group>();
            guestUser.GroupList.Add(guestGroup);
            guestUser.GroupList.Add(testGroup);

            // save user (message and group are cascaded)
            DAOFactory.UserDAO.Save(guestUser);

            //	Create JUnit user
            //
            User junitUser = CreateUser();
            junitUser.Login = JUNIT_LOGIN;
            junitUser.Password = "junit";
            junitUser.FirstName = "Unit";
            junitUser.LastName = "Test";

            junitUser.Gender = genderMale;

            // address
            address = CreateAddress();
            address.Street = "Main street";
            address.City = "Castle Rock";
            address.Country = CreateCountry();
            address.Country.Name = "United States of America";
            junitUser.Address = address;

            // create message
            Message junitMessage1 = CreateMessage();
            junitMessage1.Text = "JUnit first message";
            junitMessage1.Date = DateTime.Now;
            junitMessage1.Author = junitUser;
            ComputeKeywords(junitMessage1);
            Message junitMessage2 = CreateMessage();
            junitMessage2.Text = "JUnit first message";
            junitMessage2.Date = DateTime.Now;
            junitMessage2.Author = junitUser;
            ComputeKeywords(junitMessage2);

            junitUser.MessageList = new HashedSet<Message>();
            junitUser.MessageList.Add(junitMessage1);
            junitUser.MessageList.Add(junitMessage2);

            // group
            junitUser.GroupList = new List<Group>();
            junitUser.GroupList.Add(testGroup);

            // save user (message and group are cascaded)
            DAOFactory.UserDAO.Save(junitUser);

            //	Create Employee user
            //
            Employee employee = CreateEmployee();
            employee.Login = EMPLOYEE_LOGIN;
            employee.Password = "employee";
            employee.FirstName = "John";
            employee.LastName = "Doe";
            employee.Email = "john.doe@gilead.com";

            employee.Gender = genderUnknown;

            // address
            address = CreateAddress();
            address.Street = "Champs Elysee";
            address.City = "Paris";
            address.Country = CreateCountry();
            address.Country.Name = "France";
            employee.Address = address;

            // create message
            Message employeeMessage = CreateMessage();
            employeeMessage.Text = "John Doe's message";
            employeeMessage.Date = DateTime.Now;
            employeeMessage.Author = employee;
            ComputeKeywords(employeeMessage);
            employee.MessageList = new HashedSet<Message>();
            employee.MessageList.Add(employeeMessage);

            // group
            employee.GroupList = new List<Group>();
            employee.GroupList.Add(guestGroup);

            // save user (message and group are cascaded)
            DAOFactory.UserDAO.Save(employee);
        }

        /// <summary>
        /// Check that the database has a lot of data
        /// </summary>
        /// <returns>true/false</returns>
        public static bool IsLotOfDataCreated()
        {
            return (DAOFactory.UserDAO.LoadUserByLogin(VOLUMETRY_LOGIN) != null);
        }

        /// <summary>
        /// Initialise DB with lot of data
        /// </summary>
        /// <param name="messageCount">Message count</param>
        public static void InitializeLotOfData(int messageCount)
        {
            //	Create volumetry
            //
            User bigUser = CreateUser();
            bigUser.Login = VOLUMETRY_LOGIN;
            bigUser.FirstName = "Performance";
            bigUser.LastName = "Test";
            bigUser.MessageList = new HashedSet<Message>();

            // address
            Address address = CreateAddress();
            address.Street = "Baker street";
            address.City = "Big City";
            address.Country = CreateCountry();
            address.Country.Name = "Neverland";
            bigUser.Address = address;

            for (int index = 0; index < messageCount; index++)
            {
                // create message
                Message message = CreateMessage();
                message.Text = "Message " + index;
                message.Date = new DateTime();
                message.Author = bigUser;
                ComputeKeywords(message);
                bigUser.MessageList.Add(message);

                // batch save
                if (index % 10 == 0)
                {
                    DAOFactory.UserDAO.Save(bigUser);
                }
            }

            // save user
            DAOFactory.UserDAO.Save(bigUser);
        }

        /// <summary>
        /// Get an existing user ID
        /// </summary>
        /// <returns>Id</returns>
        public static Int64? GetExistingUserId()
        {
            //	Load user list
            //
            IList<User> userList = DAOFactory.UserDAO.LoadAll();

            //if ((userList == null) || (userList.isEmpty() == true))
            if ((userList == null) || (userList.Count == 0))
            {
                return null;
            }
            else
            {
                //	Return first element
                //
                //return userList.get(0).getId();
                return userList[0].Id;
            }
        }

        /**
         * @return an existing message ID
         */
        public static Int64? GetExistingMessageId()
        {
            //	Load user list
            //
            IList<Message> messageList = DAOFactory.MessageDAO.LoadAllMessage(0, 1);

            if ((messageList == null) || (messageList.Count == 0))
            {
                return null;
            }
            else
            {
                //	Return first element
                //
                return messageList[0].Id;
            }
        }

        /**
         * Compute keywords for message
         * @param message the message to save
         */
        public static void ComputeKeywords(Message message)
        {
            if (message.Keywords == null)
                message.Keywords = new Dictionary<String, Int64>();
            else
                message.Keywords.Clear();

            //	Computation of keywords (fake, of course)
            //
            String text = message.Text;

            // Keywords update
            StringTokenizer tokenizer = new StringTokenizer(text);
            //while (tokenizer.hasMoreTokens())
            //{
            //    String token = tokenizer.nextToken();
            //    if (token.length() > 3)
            //    {
            //        message.addKeyword(token, token.length());
            //    }
            //}
            foreach (String token in tokenizer)
            {
                if (token.Length > 3)
                {
                    message.Keywords.Add(token, token.Length);
                }
            }
        }

        #endregion

        #region Create Models

        /// <summary>
        /// Create a new user (depends on the server configuration)
        /// </summary>
        /// <returns>User</returns>
        public static User CreateUser()
        {
            return (User)Activator.CreateInstance(NhibernateContext.GetModelType(typeof(User)));
        }

        /// <summary>
        /// Create a new employee (depends on the server configuration)
        /// </summary>
        /// <returns>Employee</returns>
        public static Employee CreateEmployee()
        {
            return (Employee)Activator.CreateInstance(NhibernateContext.GetModelType(typeof(Employee)));
        }

        /// <summary>
        /// Create a new message (depends on the server configuration)
        /// </summary>
        /// <returns>Message</returns>
        public static Message CreateMessage()
        {
            return (Message)Activator.CreateInstance(NhibernateContext.GetModelType(typeof(Message)));
        }

        /// <summary>
        /// Create a new group (depends on the server configuration)
        /// </summary>
        /// <param name="id">Id</param>
        /// <returns>Group</returns>
        private static Gender CreateGender(String id)
        {
            Gender gender = (Gender)Activator.CreateInstance(NhibernateContext.GetModelType(typeof(Gender)));
            switch (id)
            {
                case "F":
                    gender.Id = "F";
                    gender.Name = "Female";
                    break;
                case "M":
                    gender.Id = "M";
                    gender.Name = "Male";
                    break;
                default:
                    gender.Id = "U";
                    gender.Name = "Unknown";
                    break;
            }
            return gender;
        }

        /// <summary>
        /// Create a new group (depends on the server configuration)
        /// </summary>
        /// <returns>Group</returns>
        public static Group CreateGroup()
        {
            return (Group)Activator.CreateInstance(NhibernateContext.GetModelType(typeof(Group)));
        }

        /// <summary>
        /// Create a new address (depends on the server configuration)
        /// </summary>
        /// <returns>Address</returns>
        public static Address CreateAddress()
        {
            return (Address)Activator.CreateInstance(NhibernateContext.GetModelType(typeof(Address)));
        }

        /// <summary>
        /// Create a new country (depends on the server configuration)
        /// </summary>
        /// <returns>Country</returns>
        public static Country CreateCountry()
        {
            return (Country)Activator.CreateInstance(NhibernateContext.GetModelType(typeof(Country)));
        }

        /// <summary>
        /// Create a country
        /// </summary>
        /// <param name="name">Name</param>
        /// <returns>Country</returns>
        public static Country CreateModelCountry(String name)
        {
            Country country = CreateCountry();
            country.Name = name;

            return country;
        }

        /// <summary>
        /// Create an address
        /// </summary>
        /// <returns>Address</returns>
        public static Address CreateModelAddress()
        {
            // address
            Address address = CreateAddress();
            address.Street = "Baker street";
            address.City = "London";
            address.Country = CreateCountry();
            address.Country.Name = "England";

            return address;
        }

        /// <summary>
        /// Create a user
        /// </summary>
        /// <returns>User</returns>
        public static User CreateModelUser()
        {
            return CreateModelUser("MODEL_USER");
        }

        /// <summary>
        /// Create a user
        /// </summary>
        /// <param name="login">Login</param>
        /// <returns>User</returns>
        public static User CreateModelUser(String login)
        {
            //	Create guest & test group
            //
            Group guestGroup = CreateGroup();
            guestGroup.Name = GUEST_GROUP;

            Group testGroup = CreateGroup();
            testGroup.Name = TEST_GROUP;

            //	Create guest user (no password)
            //
            User guestUser = CreateUser();
            guestUser.Login = login;
            guestUser.FirstName = "No";
            guestUser.LastName = "name";

            guestUser.Gender = DAOFactory.GenderDAO.LoadById("U");

            // address
            Address address = CreateAddress();
            address.Street = "Baker street";
            address.City = "London";
            address.Country = CreateCountry();
            address.Country.Name = "England";
            guestUser.Address = address;

            // create welcome message
            Message guestMessage = CreateMessage();
            guestMessage.Text = "Welcome in Gilead sample application";
            guestMessage.Date = DateTime.Now;
            guestMessage.Author = guestUser;
            ComputeKeywords(guestMessage);
            guestUser.MessageList = new HashedSet<Message>();
            guestUser.MessageList.Add(guestMessage);

            // group
            guestUser.GroupList = new List<Group>();
            guestUser.GroupList.Add(guestGroup);
            guestUser.GroupList.Add(testGroup);

            return guestUser;
        }

        #endregion

        #endregion
    }
}